/*
**Brainfuck Virtual Machine
*/

#include <stdio.h>


/***Configure***/
#define STACK_OVER_FLAG 65536

/***type define***/
typedef enum _OPCODE{
	in=1,
	out=2,
	ls=3,
	le=4,
	dd=6,
	di=5,
	pd=8,
	pi=7,
	end=0
} opcode;

/***Runtime structure***/
//stacks
unsigned char data_stack[STACK_OVER_FLAG]="";
opcode instruction_stack[STACK_OVER_FLAG]={end};
unsigned char io_stack[STACK_OVER_FLAG]="";
int cache_stack[STACK_OVER_FLAG]={-1};
//Pointers
int data_pointer=0,instruction_pointer=0,cache_pointer=0;
//Registers
#define data_register data_stack[data_pointer]
#define instruction_register instruction_stack[instruction_pointer]
#define cache_register cache_stack[cache_pointer]

#define push_cache(x) (cache_pointer++,cache_register=x)
#define pop_cache() (instruction_pointer=cache_register)
#define flush_cache() (cache_register=0,cache_pointer--)

/***Non-Runtime structure***/
char source_stack[STACK_OVER_FLAG]="";
unsigned char bytecode_source[STACK_OVER_FLAG]="";
int source_pointer=0,bytecode_pointer=0;

/***Files***/
void get_source(const char* source_file);
void put_bytecode(const char* bytecode_file);
void load_bytecode(const char* bytecode_file);

/***Compilers***/
void source_to_bytecode();
void bytecode_to_instruction();

/***Executors***/
void instruction_decoder();
void debugger();


int main(int argc, char *argv[]) {
	if(!(argc-2)||!(argc-1)){
		puts("Too few arguments");
		exit(0);
	}else if(!strcmp(argv[1],"-c")||!strcmp(argv[1],"--compile") ){
		get_source(argv[2]);
		source_to_bytecode();
		put_bytecode(argv[3]);
	}else if(!strcmp(argv[1],"-e")||!strcmp(argv[1],"--execute") ){
	    load_bytecode(argv[2]);
		bytecode_to_instruction();
		instruction_decoder();
	}else if(!strcmp(argv[1],"-i")||!strcmp(argv[1],"--interpret") ){
		get_source(argv[2]);
		source_to_bytecode();
		put_bytecode("temp.byte");
		load_bytecode("temp.byte");
		bytecode_to_instruction();
        instruction_decoder();
	}else if(!strcmp(argv[1],"--debug")||!strcmp(argv[1],"-d") ){
	    load_bytecode(argv[2]);
		bytecode_to_instruction();
		debugger();
	}else{
        puts("Wrong arguments.");
	}
}


void source_to_bytecode(){
	char c;
	unsigned char byte=0;
	source_pointer=bytecode_pointer=0;
	while(c=source_stack[source_pointer]){
		switch(c){
			case '+':
				source_pointer++;
				byte+=(di*0x10);
				break;
			case '-':
				source_pointer++;
				byte+=(dd*0x10);
				break;
			case '>':
				source_pointer++;
				byte+=(pi*0x10);
				break;
			case '<':
				source_pointer++;
				byte+=(pd*0x10);
				break;
			case '[':
				source_pointer++;
				byte+=(ls*0x10);
				break;
			case ']':
				source_pointer++;
				byte+=(le*0x10);
				break;
			case '.':
				source_pointer++;
				byte+=(out*0x10);
				break;
			case ',':
				source_pointer++;
				byte+=(in*0x10);
				break;
			case '\0':
				byte+=end;
				break;
		}
		switch(source_stack[source_pointer]){
			case '+':
				source_pointer++;
				byte+=(di);
				break;
			case '-':
				source_pointer++;
				byte+=(dd);
				break;
			case '>':
				source_pointer++;
				byte+=(pi);
				break;
			case '<':
				source_pointer++;
				byte+=(pd);
				break;
			case '[':
				source_pointer++;
				byte+=(ls);
				break;
			case ']':
				source_pointer++;
				byte+=(le);
				break;
			case '.':
				source_pointer++;
				byte+=(out);
				break;
			case ',':
				source_pointer++;
				byte+=(in);
				break;
			case '\0':
				byte+=(end);
				break;
		}
		bytecode_source[bytecode_pointer++]=byte;
		byte=0;
	}
}

void get_source(const char* sf){
	FILE* file_pointer;
	char c;
	file_pointer = fopen(sf,"r");
	if( !file_pointer ){
		perror("Error: Could not open program file");
		exit(-1);
	}
	while((c=fgetc(file_pointer))!=EOF)
		if(c=='<'||c=='>'||c==','||c=='.'||c=='-'||c=='+'||c=='['||c==']')
			source_stack[source_pointer++] = c;
	fclose(file_pointer);
}

void load_bytecode(const char* bytecode_file){
    FILE* file_pointer;
	char c;
	bytecode_pointer=0;
	file_pointer = fopen(bytecode_file,"rb");
    if( !file_pointer ){
		perror("Error: Could not open byte file");
		exit(-1);
	}
	while((c=fgetc(file_pointer))!=EOF)
		bytecode_source[bytecode_pointer++]=c;
    fclose(file_pointer);
}

void bytecode_to_instruction(){
	unsigned char c;
	instruction_pointer=bytecode_pointer=0;
	while(c=bytecode_source[bytecode_pointer]){
        instruction_stack[instruction_pointer]=c/0x10;
		instruction_pointer++;
		instruction_stack[instruction_pointer]=c%0x10;
		instruction_pointer++;
		bytecode_pointer++;
	}
    instruction_pointer=0;
}

/***Put Files***/
void put_bytecode(const char* bytecode_file){
	FILE* file_pointer;
	file_pointer = fopen(bytecode_file,"wb");
	if( !file_pointer ){
		perror("Error: Could not open program file");
		exit(-1);
	}
	fputs(bytecode_source,file_pointer);
	fclose(file_pointer);
}

/***Executors***/
void instruction_decoder(){
    opcode c;
    instruction_pointer=data_pointer=0;
	while(c=instruction_stack[instruction_pointer]){
	    if(!(cache_register+1)&&cache_pointer&&c!=le){	//if flow-control was not avalible
			instruction_pointer++;
			continue;
		}else{
			switch(c){
				case pi:
					((data_pointer!=STACK_OVER_FLAG-1)?(data_pointer++):(data_pointer=0));
					break;
				case pd:
					((data_pointer)?(data_pointer--):(data_pointer=STACK_OVER_FLAG-1));
					break;
				case di:
					data_register++;
					break;
				case dd:
					data_register--;
					break;
				case out:
					putchar(data_register);
					break;
				case in:
					data_register=getchar();
					break;
				case ls:
					(data_register?push_cache(instruction_pointer):push_cache(-1));
					break;
				case le:
					(data_register&&(cache_register+1)?pop_cache():flush_cache());
					break;
				default:
					break;
			}
			instruction_pointer++;
		}
	}
}

/***Debuggers***/
void debugger(){
    opcode c;
    instruction_pointer=data_pointer=0;
    puts("BFM debugging:");
    puts("Load instructions now:");
	while(c=instruction_stack[instruction_pointer]){
	    if(!(cache_register+1)&&cache_pointer&&c!=le){	//if flow-control was not avalible
			instruction_pointer++;
			continue;
		}else{
			switch(c){
				case pi:
					((data_pointer!=STACK_OVER_FLAG-1)?(data_pointer++):(data_pointer=0));
					break;
				case pd:
					((data_pointer)?(data_pointer--):(data_pointer=STACK_OVER_FLAG-1));
					break;
				case di:
					data_register++;
					break;
				case dd:
					data_register--;
					break;
				case out:
					printf("output:%c\n",data_register);
					break;
				case in:
					data_register=getchar();
					break;
				case ls:
					(data_register?push_cache(instruction_pointer):push_cache(-1));
					break;
				case le:
					(data_register&&(cache_register+1)?pop_cache():flush_cache());
					break;
				default:
					break;
			}
            printf("CI:%.2d\tNI:%.2d\tCD:%.3d\tCP:%.5d",
				instruction_register,
				instruction_stack[instruction_pointer+1],
				data_register,data_pointer);
            getchar();
			instruction_pointer++;
		}
	}
}

